package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.list.model.*;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.product.model.BaseAttrInfo;
import com.atguigu.gmall.product.model.BaseCategoryView;
import com.atguigu.gmall.product.model.BaseTrademark;
import com.atguigu.gmall.product.model.SkuInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * @Author: 小兰
 * @Date: 2023/4/26 19:34
 */
@Service
@Slf4j
@SuppressWarnings("all")
public class SearchServiceImpl implements SearchService {
    @Autowired
    private ProductFeignClient productFeignClient;
    public  static final String index_name = "goods";
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private RedisTemplate redisTemplate;


    /**
     * todo 测试接口 将指定商品skuid封装为索引库商品文档对象,将商品存入索引库
     *
     * @param skuId
     * @return
     */
    @Override
    public void upperGoods(Long skuId) {
        try {
        //构建索引库文档对象goods
        Goods goods=new Goods();
        goods.setId(skuId);
        //1.远程调用商品微服务根据商品skuid查询商品基本信息
            CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
                SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
                if (skuInfo != null) {
                    goods.setCategory3Id(skuInfo.getCategory3Id());
                    goods.setDefaultImg(skuInfo.getSkuDefaultImg());
                    goods.setTitle(skuInfo.getSkuName());
                    goods.setTmId(skuInfo.getTmId());
                }
                return skuInfo;
            },threadPoolExecutor);


            CompletableFuture.runAsync(()->{
                goods.setPrice(productFeignClient.getSkuPrice(skuId).doubleValue());
            },threadPoolExecutor);



            //2远程调用商品微服务-根据分类id查询分类信息
            CompletableFuture<Void> categoryCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync((skuInfo) -> {
                BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
                if (categoryView != null) {
                    goods.setCategory1Id(categoryView.getCategory1Id());
                    goods.setCategory1Name(categoryView.getCategory1Name());
                    goods.setCategory2Id(categoryView.getCategory2Id());
                    goods.setCategory2Name(categoryView.getCategory2Name());
                    goods.setCategory3Id(categoryView.getCategory3Id());
                    goods.setCategory3Name(categoryView.getCategory3Name());

                }
            }, threadPoolExecutor);


            //3.远程调用商品微服务-根据品牌id查询品牌id
            CompletableFuture<Void> priceCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
                BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
                if (trademark != null) {
                    goods.setTmId(trademark.getId());
                    goods.setTmName(trademark.getTmName());
                    goods.setTmLogoUrl(trademark.getLogoUrl());

                }
            }, threadPoolExecutor);


            //4.远程调用微服务-根据skuid查询平台属性
            CompletableFuture<Void> attrInfoCompletableFuture = CompletableFuture.runAsync(() -> {
                List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
                if (!CollectionUtils.isEmpty(attrList)) {
                    List<SearchAttr> searchAttrList = attrList.stream().map(baseAttrInfo -> {
                        SearchAttr searchAttr = new SearchAttr();
                        searchAttr.setAttrId(baseAttrInfo.getId());
                        searchAttr.setAttrName(baseAttrInfo.getAttrName());
                        searchAttr.setAttrValue(baseAttrInfo.getAttrValue());
                        return searchAttr;

                    }).collect(Collectors.toList());
                    goods.setAttrs(searchAttrList);
                }

            }, threadPoolExecutor);
            goods.setCreatedDate(new Date());
            goods.setCreateTime(new Date());
            CompletableFuture.allOf(
                    skuInfoCompletableFuture,
                    priceCompletableFuture,
                    attrInfoCompletableFuture,
                    categoryCompletableFuture
            ).join();
            //5.调用JavaHighLevelRestClient完成文档的新增  本质上发起的还是http请求
        //5.1  创建文档请求对象  indexrequest  封装新增数据对应索引库库名称 文档id
        IndexRequest request = new IndexRequest(index_name).id(skuId.toString());


        //5.2  构建新增文档请求体参数  json
        request.source(JSON.toJSONString(goods), XContentType.JSON);
        //5.3 发起请求,,请求ES
        //拿到客户端对象,第二个参数是认证,,比如头信息是什么

            restHighLevelClient.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("[搜索服务]上架商品:{},失败原因:{}", skuId, e);
            throw new RuntimeException(e);
        }

    }

    /**
     * 测试接口，商品文档删除
     *
     * @param skuId
     * @return
     */
    @Override
    public void lowerGoods(Long skuId) {

        try {
            DeleteRequest request=new DeleteRequest(index_name,skuId.toString());
            restHighLevelClient.delete(request,RequestOptions.DEFAULT);
        } catch (IOException e) {
            log.error("[搜索服务]下架商品:{},失败原因:{}", skuId, e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 提供给详情服务调用:更新商品热度分值
     *
     * @param skuId
     */
    @Override
    public void incrHotScore(Long skuId) {

            try {
                //1.更新Redis缓冲中商品分值
                String key = "hotScore";
                Double skuHotScore = redisTemplate.opsForZSet().incrementScore(key, skuId.toString(), 1);
                //2.更新ES文档中商品分值(稀释写操作,,没查询10次,es更新一次)
                if(skuHotScore%10==0){
                    //创建更新文档请求对象,包含文档id,文档分值
                    UpdateRequest request = new UpdateRequest(index_name,skuId.toString());
                    Goods goods = new Goods();
                    goods.setHotScore(skuHotScore.longValue());
                    request.doc(JSON.toJSONString(goods),XContentType.JSON);
                restHighLevelClient.update(request,RequestOptions.DEFAULT);
            }

            }
            catch (IOException e) {
                log.error("[搜索微服务]更新商品分值:{},异常信息:{}", skuId, e);
                throw new RuntimeException(e);
            }




    }

    /**
     * 商品检索功能!
     *
     * @param searchParam
     * @return
     */
    @Override
    public SearchResponseVo search(SearchParam searchParam) {

        try {
            //构建请求对象-封装请求方式,请求体参数
            SearchRequest searchRequest=this.builderDSL(searchParam);
            //执行检索
            SearchResponse  response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

            //封装响应结果

            return this.parseResult(response,searchParam);
        } catch (IOException e) {
            log.error("搜索服务异常尼龙啊!",e,searchParam);
            throw new RuntimeException(e);
        }

    }



    /**
     * 构建查询请求对象,构建DSL语句中请求跟请求参数
     * @param searchParam
     * @return
     */

    private SearchRequest builderDSL(SearchParam searchParam) {
        //构建请求对象-封装请求方式,请求体参数
        //1.构建搜索请求对象 封装 good/_search 请求方式:请求路径参数中索引库名称
        SearchRequest searchRequest = new SearchRequest(index_name);

        //2.构建请求体构建器对象 封装请求体JSON参数 例如:查询;分页;高亮;排序;字段指定;聚合
        SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
        //2.1设置查询条件,请求参数中"query"得部分
        //2.1.1构建所有封装布尔查询条件对象
        BoolQueryBuilder allBoolQueryBuilder= QueryBuilders.boolQuery();
        //2.1.2根据关键字检索-采用匹配/模糊/关键字查询
        if (StringUtils.isNotBlank(searchParam.getKeyword())){
            allBoolQueryBuilder.must(QueryBuilders.
                    matchQuery("title",searchParam.getKeyword()).operator(Operator.AND));

        }

        //2.1.3设置布尔查寻条件中过滤 1.对品牌过滤,,2.平台属性过滤
        //2.1.3.1设置品牌过滤
        if (StringUtils.isNotBlank(searchParam.getTrademark())){
            String[] split = searchParam.getTrademark().split(":");
            //第一位是品牌id,第二位是品牌名称
            if (split!=null && split.length==2){
                allBoolQueryBuilder.filter(QueryBuilders.termQuery("tmId",split[0]));
            }
        }
        //2.1.3.2设置 分类过滤
        if (searchParam.getCategory1Id()!=null){
            allBoolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
        }
        if (searchParam.getCategory2Id()!=null){
            allBoolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
        }
        if (searchParam.getCategory3Id()!=null){
            allBoolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
        }
        //2.1.3.3设置平台属性过滤
        //构建封装所有平台属性的bool查询
        BoolQueryBuilder allAttrBoolQueryBuilder = QueryBuilders.boolQuery();
        if (searchParam.getProps()!=null && searchParam.getProps().length>0){
            //动态构建某个平台属性过滤条件
            //调一个过来就是一个nested查询,所以要遍历
            for (String prop : searchParam.getProps()) {
                String[] split = prop.split(":");
                if (split!=null && split.length==3){
                    //第一位是平台属性id,第二位是平台属性值,第三位是平台属性名称
                    //每循环一次,就构建一次nested查询
                    //每一组平台属性过滤条件,又是一个bool查询
                    BoolQueryBuilder attrBoolQueryBuilder = QueryBuilders.boolQuery();
                    attrBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
                    attrBoolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));
                    NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", attrBoolQueryBuilder, ScoreMode.None);
                    allAttrBoolQueryBuilder.must(nestedQuery);
                }
            }
        }

        allBoolQueryBuilder.filter(allAttrBoolQueryBuilder);
        sourceBuilder.query(allBoolQueryBuilder);

        //2.2设置高亮, 请求参数中highlight    高亮前提 用户采用关键字检索  isBlank:不为空  isEmpty:为空  ""空字符串
        if (StringUtils.isNotBlank(searchParam.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            //2.2.1 设置前置标签
            highlightBuilder.preTags("<font  style='color:red'>");
            //2.2.2 设置后置标签
            highlightBuilder.postTags("</font>");
            //2.2.3 设置高亮字段
            highlightBuilder.field("title");
            sourceBuilder.highlighter(highlightBuilder);

        }
        //2.3设置分页,请求参数 ""from    size""
        //2.3.1计算起始位置索引 (pageNo-1)*pageSize
        int from= (searchParam.getPageNo()-1)*searchParam.getPageSize();
        sourceBuilder.from(from).size(searchParam.getPageSize());
        //2.4设置排序,请求参数"sort"  前提: 前端提交排序条件 参数形式 数组第二位:asc 1:按照热度排 2:价格
        if (StringUtils.isNotBlank(searchParam.getOrder())){
            String[] split = searchParam.getOrder().split(":");
            if (split!=null && split.length==2){
                String orderField="";
                if (split[0].equals("1")){
                   //按照热度
                    orderField="hotScore";
                }else if (split[0].equals("2")){
                    orderField="price";
                }
                sourceBuilder.sort(orderField,"asc".equals(split[1])? SortOrder.ASC:SortOrder.DESC);
            }

        }

        //2.5设置响应文档字段列表 ,请求参数"_source"
        sourceBuilder.fetchSource(new String[]{"id","title","price","defaultImg"},null);
        //2.6设置聚合,请求参数"aggs"  ,聚合 ,每次都得做
        //2.6.1设置品牌聚合
        //2.6.1.1先创建品牌id聚合对象   terms("里面是聚合的名称")
        TermsAggregationBuilder tmIdAggregation = AggregationBuilders.terms("tmIdAgg").field("tmId");
        //2.6.1.2 基于平拍ID聚合对象设置子聚合-品牌名称聚合
        tmIdAggregation.subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"));
        //2.6.1.3 基于平拍ID聚合对象设置子聚合-品牌logo聚合
        tmIdAggregation.subAggregation(AggregationBuilders.terms("tmLogoAgg").field("tmLogoUrl"));
        sourceBuilder.aggregation(tmIdAggregation);
        //2.6.2设置平台属性聚合
        //2.6.2.1创建嵌套类型nested平台属性聚合对象
        NestedAggregationBuilder nestedAggregation = AggregationBuilders.nested("attrAgg", "attrs");
        //2.6.2.2创建平台属性ID聚合对象
        TermsAggregationBuilder attrIdAggregation = AggregationBuilders.terms("attrId").field("attrs.attrId");
        //2.6.2.2.1基于平台属性ID聚合对象 创建子聚合  平台属性名称对象
        attrIdAggregation.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"));
        //2.6.2.2.2基于平台属性ID聚合对象 创建子聚合  平台属性值对象
        attrIdAggregation.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"));
        //2.6.3将平台属性ID聚合对象加入到平台属性聚合对象中
        nestedAggregation.subAggregation(attrIdAggregation);
        sourceBuilder.aggregation(nestedAggregation);
        //3.封装检索到商品数据-注意处理高亮字段
        //在搜索请求对象中添加请求体构建器对象
        System.err.println(sourceBuilder.toString());
        return searchRequest.source(sourceBuilder);
    }
    private SearchResponseVo parseResult(SearchResponse response, SearchParam searchParam) {
        //封装响应结果
        //构建响应Vo对象
        SearchResponseVo searchResponseVo=new SearchResponseVo();
        //1.封装响应Vo对象中分页信息
        Integer pageSize=searchParam.getPageSize();
        searchResponseVo.setPageSize(pageSize);
        searchResponseVo.setPageNo(searchParam.getPageNo());
        long totle = response.getHits().getTotalHits().value;
        searchResponseVo.setTotal(totle);
        Long totlePages =  totle%pageSize==0?(totle/pageSize):(totle/pageSize+1);

        searchResponseVo.setTotalPages(totlePages);

        //2.封装响应Vo对象中当前页业务数据
        SearchHit[] hits = response.getHits().getHits();
        if(hits!=null && hits.length>0){
            //遍历啊命中数据得到业务JSON,将JSON反序列化为Java对象goods
            List<SearchHit> searchHits = Arrays.asList(hits);
            List<Goods> goodsList = searchHits.stream().map(hit -> {
                //获取商品文档json数据
                String goodsJsonStr = hit.getSourceAsString();
                //转为goods对象
                Goods goods = JSON.parseObject(goodsJsonStr, Goods.class);
                //获取高亮
                Map<String, HighlightField> highlightFields = hit.getHighlightFields();
                if (!CollectionUtils.isEmpty(highlightFields)) {
                    //获取标题高亮结果对象
                    HighlightField highlightField = highlightFields.get("title");
                    if (highlightField != null) {
                        //获取高亮片段数组
                        Text[] fragments = highlightField.getFragments();
                        if (fragments != null && fragments.length > 0) {
                            //获取高亮内容
                            String s = fragments[0].toString();
                            goods.setTitle(s);
                        }
                    }
                }
                return goods;
            }).collect(Collectors.toList());
            searchResponseVo.setGoodsList(goodsList);
        }
        //3.封装响应Vo对象中用于展示过滤项-品牌聚合结果
        //3.1获取所有的聚合结果
        Map<String, Aggregation> allAggregationMap = response.getAggregations().asMap();
        //3.2获取品牌id聚合结果对象,将聚合结果Aggregation接口转为实现类 parsedXXterms
        ParsedLongTerms tmIdAgg =(ParsedLongTerms) allAggregationMap.get("tmIdAgg");
        //3.2.1从tmid聚合对象中获取bucket数组
        List<? extends Terms.Bucket> buckets = tmIdAgg.getBuckets();
        if (!CollectionUtils.isEmpty(buckets)) {
            List<SearchResponseTmVo> responseTmVos = buckets.stream().map(bucket -> {
                SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
                //遍历bucket数组获取品牌bucket对象
                long tmId = bucket.getKeyAsNumber().longValue();
                //3.2.2居于品牌id聚合结果对象获取子聚合对象-品牌名称,,品牌Logo
                Map<String, Aggregation> tmNameAndLogoAggregationMap = bucket.getAggregations().asMap();
                if (!CollectionUtils.isEmpty(tmNameAndLogoAggregationMap)) {
                    //获取品牌名称
                    ParsedStringTerms tmNameAgg = (ParsedStringTerms) tmNameAndLogoAggregationMap.get("tmNameAgg");
                    String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
                    ParsedStringTerms tmLogoAgg = (ParsedStringTerms) tmNameAndLogoAggregationMap.get("tmLogoAgg");
                    String tmLogo = tmLogoAgg.getBuckets().get(0).getKeyAsString();
                    searchResponseTmVo.setTmName(tmName);
                    searchResponseTmVo.setTmLogoUrl(tmLogo);
                }
                searchResponseTmVo.setTmId(tmId);
                return searchResponseTmVo;
            }).collect(Collectors.toList());
            searchResponseVo.setTrademarkList(responseTmVos);
        }
        //4.封装响应Vo对象中用于展示过滤项-平台属性聚合结果
        //4.1获取平台属性nested聚合对象
        ParsedNested attrAgg = (ParsedNested) allAggregationMap.get("attrAgg");
        //4.2基于平台属性聚合对象获取平台属性id子聚合对象
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrId");
        //4.3通过平台属性聚合对象获取聚合结果bucket数组,遍历bucket数组,获取平台属性,名称,属性值
        List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
        if (!CollectionUtils.isEmpty(attrIdAggBuckets)) {
            List<SearchResponseAttrVo> attrVoList = attrIdAggBuckets.stream().map(bucket -> {
                SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
                //4.3.1通过bucket对象获取聚合倒平台属性id
                long attrId = bucket.getKeyAsNumber().longValue();
                searchResponseAttrVo.setAttrId(attrId);
                //4.3.2通过bucket对象获取平台属性名称子聚合对象
                ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
                if (!CollectionUtils.isEmpty(attrNameAgg.getBuckets())) {
                    String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                    searchResponseAttrVo.setAttrName(attrName);
                }
                //4.3.3通过bucket对象获取平台属性值子聚合对象
                ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
                List<? extends Terms.Bucket> valueAggBuckets = attrValueAgg.getBuckets();
                if (!CollectionUtils.isEmpty(valueAggBuckets)) {
                    List<String> attrValueList = valueAggBuckets.stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
                    searchResponseAttrVo.setAttrValueList(attrValueList);
                }
                return searchResponseAttrVo;
            }).collect(Collectors.toList());
            searchResponseVo.setAttrsList(attrVoList);
        }
        return searchResponseVo;
    }
}
